/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - geometry/containment
// Copyright( c) 2015.  All Rights Reserved
//
// File:		AEAlignedBoundingBox.h
// Author:		Gianluca Belardelli
// Date:		19/06/2015
//
/////////////////////////////////////////////////////////////////////////
#ifndef _AEALIGNEDBOUNDINGBOX_H_
#define _AEALIGNEDBOUNDINGBOX_H_

/// \brief
///   An axis aligned bounding box class.
class AEAlignedBoundingBox
{
// Members
public:
	/// \brief The minimum corner of the box.
	AEVector3f m_vcMin;

	/// \brief The maximum corner of the box.
	AEVector3f m_vcMax;

public:
	/** @name Constructors */
	/// @{
	/// \brief
	///   ATTENTION: The object is NOT initialized by the constructor. You MUST initialize it yourself before using it.
	///
	/// \note In Dev and Debug builds the object will be initialized with NaN values. Member functions that read the values will check that they are not NaN.
	/// If an NaN value is encountered, those functions will trigger an assert. Thus when you run into such an assert, you have not initialized your object
	/// after construction. Make sure you always initialize objects properly before using them.
	AE_FORCEINLINE AEAlignedBoundingBox( void );

	/// \brief Does not initialize this object. Prefer this constructor over the default constructor.
	/// \param init
	///   Set this to hkvNoInitialization to actually select this constructor.
	AE_FORCEINLINE explicit AEAlignedBoundingBox( AEInit_None inInit );

	/// \brief Initializes the box with the given min and max values.
	AE_FORCEINLINE AEAlignedBoundingBox( const AEVector3f &vcMin, const AEVector3f &vcMax );

	/// @}
	/// @name Setup
	/// @{
	/// \brief Sets the box to all zero.
	/// \note This is a valid bounding box with a volume of zero.
	AE_FORCEINLINE void SetZero( void );

	/// \brief Sets the box's min and max values from the given center and half-size extents.
	AE_FORCEINLINE void SetCenterAndSize( const AEVector3f &vcCenter, const AEVector3f &vcHalfSizeXYZ );

	/// \brief Sets the box's min and max values. In debug mode this will check that vcMin <= vcMax, to ensure a valid bbox.
	AE_FORCEINLINE void Set( const AEVector3f &vcMin, const AEVector3f &vcMax );

	/// \brief Sets the box's min and max values. Never checks whether vcMin <= vcMax, to allow to set invalid boxes. Useful when initializing data from serialized data.
	AE_FORCEINLINE void SetWithoutValidityCheck( const AEVector3f &vcMin, const AEVector3f &vcMax );

	/// \brief Sets the box to be the bounding box of the given set of points.
	/// \param lpPoints Pointer to an array holding a set of points.
	/// \param uiNumPoints Number of points that should be used from the array.
	/// \param uiStride The stride (in bytes) between each point. Must be at least sizeof (AEVector3f) for a pure AEVector3f array.
	///   can be larger, if there is other data in between the points.
	AE_DLLIMPEXP void SetFromPoints( const AEVector3f *lpPoints, AEUINT32 uiNumPoints, AEUINT32 uiStride = sizeof( AEVector3f ) );

	/// @}
	/// @name Piecewise Setup
	/// @{
	/// \brief Sets m_vcMin to the maximum positive AEFLOAT32 value, and m_vcMax to the maximum negative AEFLOAT32 value (min > max),
	/// making it an invalid box.
	/// The various expandToInclude functions can then be used to enlarge the box until it is large enough.
	/// \sa AEAlignedBoundingBox::ExpandToInclude
	AE_FORCEINLINE void SetInvalid( void );

	/// \brief Returns true if m_vcMin <= m_vcMax.
	AE_FORCEINLINE AEBOOL32 IsValid( void ) const;

	/// \brief Reduces the box such that only the intersection with the given box remains.
	/// Box is invalid afterwards if the two boxes have no overlapping region.
	/// \param abbRhs Box to intersect with the callee.
	/// \sa AEAlignedBoundingBox::IsValid
	AE_FORCEINLINE void ReduceToIntersection( const AEAlignedBoundingBox &abbRhs );

	/// \brief Enlarges the box such that v is contained in it (if necessary).
	AE_FORCEINLINE void ExpandToInclude( const AEVector3f &vcPoint );

	/// \brief Enlarges the box such that the given set of points is contained in it (if necessary).
	/// \param lpPoints Pointer to an array holding a set of points.
	/// \param uiNumPoints Number of points that should be used from the array.
	/// \param uiStride The stride (in bytes) between each point. Must be at least sizeof (AEVector3f) for a pure AEVector3f array.
	///   can be larger, if there is other data in between the points.
	AE_DLLIMPEXP void ExpandToInclude( const AEVector3f *lpPoints, AEUINT32 uiNumPoints, AEUINT32 uiStride = sizeof( AEVector3f ) );

	/// \brief Enlarges the box such that the given object is contained in it (if necessary).
	AE_FORCEINLINE void ExpandToInclude( const AEAlignedBoundingBox &abbRhs );

	/// \brief Enlarges the box such that the given object is contained in it (if necessary).
	AE_FORCEINLINE void ExpandToInclude( const AEBoundingSphere &abbRhs );

	/// \brief Makes all sides of the box as long as its longest side, thus making it a cube.
	AE_FORCEINLINE void ExpandToCube( void );

	/// \brief Changes the size by vChangeXYZ in each direction, e.g. along the positive AND negative axis.
	AE_FORCEINLINE void AddBoundary( const AEVector3f &vcChangeXYZ );

	/// @}
	/// @name Modification
	/// @{
	/// \brief Transforms the 8 corners in object-space and then recomputes the AABB.
	AE_FORCEINLINE void TransformFromCenter( const AEMatrix4f &matTransform );

	/// \brief Transforms the 8 corners in world-space and then recomputes the AABB.
	AE_FORCEINLINE void TransformFromOrigin( const AEMatrix4f &matTransform );

	/// \brief Translates the box in world-space.
	AE_FORCEINLINE void Translate( const AEVector3f &vcTranslation );

	/// \brief Scales the box's size, but does not change its position.
	AE_FORCEINLINE void ScaleFromCenter( const AEVector3f &vcScaleXYZ );

	/// \brief Scales the box's min and max extents, thus changing its size and position.
	AE_FORCEINLINE void ScaleFromOrigin( const AEVector3f &vcScaleXYZ );

	/// @}
	/// @name Checks
	/// @{
	/// \brief Checks whether the two boxes are identical.
	AE_FORCEINLINE AEBOOL32 IsIdentical( const AEAlignedBoundingBox &abbRhs ) const;

	/// \brief Checks whether the two boxes are equal withing some epsilon range.
	AE_FORCEINLINE AEBOOL32 IsEqual( const AEAlignedBoundingBox &abbRhs, AEFLOAT32 fEpsilon ) const;

	/// \brief Checks whether the box is all zero within some epsilon range.
	AE_FORCEINLINE AEBOOL32 IsZero( AEFLOAT32 fEpsilon = AEMATH_DEFAULT_EPSILON ) const;

	/// @}
	/// @name Retrieving Data
	/// @{
	/// \brief Returns the center point of the box.
	AE_FORCEINLINE const AEVector3f GetCenter( void ) const;

	/// \brief Returns the box's extent along the X axis.
	AE_FORCEINLINE AEFLOAT32 GetSizeX( void ) const;

	/// \brief Returns the box's extent along the Y axis.
	AE_FORCEINLINE AEFLOAT32 GetSizeY( void ) const;

	/// \brief Returns the box's extent along the Z axis.
	AE_FORCEINLINE AEFLOAT32 GetSizeZ( void ) const;

	/// \brief Writes the 8 corners of the box to the provided array.
	/// \param out_pVertices The array to which the corners are written. Must be large enough to store 8 vectors.
	/// \sa AEAlignedBoundingBox::GetCorner
	AE_FORCEINLINE void GetCorners( AEVector3f *lpOutVertices ) const;

	/// \brief Returns one corner of the box.
	/// \param bMaxX
	///   If set to true the X coordinate of the returned corner will be from the max extent.
	///   Otherwise it will be from the min extent.
	/// \param bMaxY
	///   If set to true the Y coordinate of the returned corner will be from the max extent.
	///   Otherwise it will be from the min extent.
	/// \param bMaxZ
	///   If set to true the Z coordinate of the returned corner will be from the max extent.
	///   Otherwise it will be from the min extent.
	/// \sa AEAlignedBoundingBox::getCorners
	AE_FORCEINLINE AEVector3f GetCorner( AEBOOL32 bMaxX, AEBOOL32 bMaxY, AEBOOL32 bMaxZ ) const;

	/// \brief Checks which of the extents (X,Y,Z) is the longest. Returns that value and its index (0,1,2).
	/// \param out_iAxis Optional. If set the index of the longest extent (0, 1 or 2) will be stored here.
	/// \return The length of the longest extent.
	AE_FORCEINLINE AEFLOAT32 GetMaxExtent( AEUINT32 *lpOutAxis = AENULL ) const;

	/// \brief Checks which of the extents (X,Y,Z) is the shortest. Returns that value and its index (0,1,2).
	/// \param out_iAxis Optional. If set the index of the shortest extent (0, 1 or 2) will be stored here.
	/// \return The length of the shortest extent.
	AE_FORCEINLINE AEFLOAT32 GetMinExtent( AEUINT32 *lpOutAxis = AENULL ) const;

	/// \brief Returns a AEBoundingSphere that perfectly fits around this box.
	AE_FORCEINLINE const AEBoundingSphere GetBoundingSphere( void ) const;

	/// @}
	/// @name Bounding Volume Checks
	/// @{
	/// \brief Returns true if the given point is inside the box.
	AE_FORCEINLINE AEBOOL32 Contains( const AEVector3f &vcPoint ) const;

	/// \brief Returns true if all points from the given set are inside the box.
	/// \param lpPoints Pointer to an array holding a set of points.
	/// \param uiNumPoints Number of points that should be used from the array.
	/// \param uiStride The stride (in bytes) between each point. Must be at least sizeof (AEVector3f) for a pure AEVector3f array.
	///   can be larger, if there is other data in between the points.
	AE_DLLIMPEXP AEBOOL32 Contains( const AEVector3f *lpPoints, AEUINT32 uiNumPoints, AEUINT32 uiStride = sizeof( AEVector3f ) ) const;

	/// \brief Returns true if the given box is inside this box.
	AE_FORCEINLINE AEBOOL32 Contains( const AEAlignedBoundingBox &abbRhs ) const;

	/// \brief Returns true if the given sphere is inside this box.
	AE_FORCEINLINE AEBOOL32 Contains( const AEBoundingSphere &bsRhs ) const;

	/// \brief Returns true if the given object is inside this box or partially overlaps it.
	AE_FORCEINLINE AEBOOL32 Overlaps( const AEAlignedBoundingBox &bsRhs ) const;

	/// \brief Returns true if the given object is inside this box or partially overlaps it.
	AE_FORCEINLINE AEBOOL32 Overlaps( const AEBoundingSphere &bsRhs ) const;

	/// @}
	/// @name Distance Checks
	/// @{
	/// \brief Returns the squared distance of the point v to this box.
	/// For points that are inside the box this function returns zero.
	/// \param vcPoint The point for which the distance is computed.
	AE_FORCEINLINE AEFLOAT32 GetDistanceToSquared( const AEVector3f &vcPoint ) const;

	/// \brief Returns the squared distance of the given box to this box.
	/// If the two boxes overlap, zero is returned.
	/// \param abbRhs The box for which the distance is computed.
	AE_FORCEINLINE AEFLOAT32 GetDistanceToSquared( const AEAlignedBoundingBox &abbRhs ) const;

	/// \brief Returns the distance of the point v to this box.
	/// For points that are inside the box this function returns zero.
	/// \param vcPoint The point for which the distance is computed.
	AE_FORCEINLINE AEFLOAT32 GetDistanceTo( const AEVector3f &vcPoint ) const;

	/// \brief Returns the distance of the given box to this box.
	/// If the two boxes overlap, zero is returned.
	/// \param abbRhs The box for which the distance is computed.
	AE_FORCEINLINE AEFLOAT32 GetDistanceTo( const AEAlignedBoundingBox &abbRhs ) const;

	/// \brief Returns the distance of the given sphere to this box.
	/// If the two objects overlap, zero is returned.
	/// \param bsRhs The sphere for which the distance is computed.
	AE_FORCEINLINE AEFLOAT32 GetDistanceTo( const AEBoundingSphere &bsRhs ) const;

	/// @}
	/// @name Intersection Tests
	/// @{
	/// \brief Checks whether the given ray intersects the surface of the box and computes the intersection time and point.
	/// If the ray start position is within the box, and intersection with the surface will always be detected.
	/// If however, you need to handle such cases as if the box was solid and the intersection point should already be
	/// at the ray start position, you should check first whether the box contains the ray start position already.
	/// \param vcRayStartPos
	///   The start position of the ray. If the start position is inside the box, an intersection will always
	///   be detected, however not at time 0, but wherever the ray hits the surface of the box.
	/// \param vcRayDir
	///   The direction of the ray. May be normalized but is not required.
	/// \param lpfIntersectionTime
	///   Optional. If set, the 'intersection-time' will be stored here.
	///   The intersection time will always be >= 0.
	///   If the ray misses the box, this value will not be modified.
	///   vcRayStartPos + vcRayDir * *lpfIntersectionTime == *lpvcIntersectionPoint
	///   That means the intersection time scales anti-proportionally to the length of vcRayDir.
	/// \param lpvcIntersectionPoint
	///   Optional. If set, the point of intersection will be stored here.
	///   If the ray misses the box, this value will not be modified.
	///   vcRayStartPos + vcRayDir * *lpfIntersectionTime == *lpvcIntersectionPoint
	/// \return
	///   True if the ray hits the box, false otherwise.
	/// \sa AEAlignedBoundingBox::GetLineSegmentIntersection
	AE_DLLIMPEXP AEBOOL32 GetRayIntersection( const AEVector3f &vcRayStartPos, const AEVector3f &vcRayDir, AEFLOAT32 *lpfIntersectionTime = AENULL, AEVector3f *lpvcIntersectionPoint = AENULL ) const;

	/// \brief Computes the intersection of a line segment and the surface of the box.
	/// If the line segment's start and end point are both within the box, no intersection will be reported.
	/// If you need to handle these cases as intersections, test first, whether the box contains the start
	/// position already.
	/// \param vcLineStartPos
	///   The start position of the line.
	///   If the start position is inside the box, an intersection will only be detected, if the end point
	///   is outside the box.
	/// \param vcLineEndPos
	///   The end position of the line.
	/// \param lpfHitFraction
	///   Optional. If set, the fraction along the line, where the intersection point is located,
	///   will be stored here. This value will always be between 0 and 1.\n
	///   E.g: vcLineStartPos + (vcLineEndPos - vcLineStartPos) * *lpfHitFraction == *lpvcIntersectionPoint\n
	///   Or in other words:\n
	///   hkvMath::interpolate (vcLineStartPos, vcLineEndPos, *lpfHitFraction) == *lpvcIntersectionPoint\n
	///   If the line does not intersect with the box, this variable will not be modified.
	/// \param lpvcIntersectionPoint
	///   Optional. If set, the intersection point will be stored here.
	///   If the line does not intersect with the box, this variable will not be modified.\n
	///   vcLineStartPos + (vcLineEndPos - vcLineStartPos) * *lpfHitFraction == *lpvcIntersectionPoint\n
	/// \return
	///   true if the line segment intersects with the box, false otherwise.
	/// \sa AEAlignedBoundingBox::getRayIntersection
	AE_FORCEINLINE AEBOOL32 GetLineSegmentIntersection( const AEVector3f &vcLineStartPos, const AEVector3f &vcLineEndPos, AEFLOAT32 *lpfHitFraction = AENULL, AEVector3f *lpvcIntersectionPoint = AENULL ) const;

	/// @}
	/// @name Utility Functions
	/// @{
	/// \brief Returns v clamped to the volume of the box.
	/// If v is already inside the box, it will be returned unchanged.
	/// If v is outside the box, its components will be clamped to the min and max extents of the box.
	/// \param v The point which shall be clamped to the box's volume.
	/// \sa AEAlignedBoundingBox::ClampPositions
	AE_FORCEINLINE AEVector3f GetClampedPosition( const AEVector3f &vcPoint ) const;

	/// \brief Clamps a set of points to the volume of the box.
	/// If a point is already inside the box, it will not be modified.
	/// If a point is outside the box, its components will be clamped to the min and max extents of the box.
	/// \param lpPoints
	///   Pointer to an array holding a set of points.
	/// \param uiNumPoints
	///   Number of points that should be used from the array.
	/// \param uiStride
	///   The stride (in bytes) between each point. Must be at least sizeof (AEVector3f) for a pure AEVector3f array.
	///   can be larger, if there is other data in between the points.
	/// \sa AEAlignedBoundingBox::getClampedPosition
	AE_DLLIMPEXP void ClampPositions( AEVector3f *lpPoints, AEUINT32 uiNumPoints, AEUINT32 uiStride = sizeof( AEVector3f ) ) const;

	/// @}
};

/// \brief Checks whether the two boxes are identical.
AE_FORCEINLINE AEBOOL32 operator== ( const AEAlignedBoundingBox &abbLhs, const AEAlignedBoundingBox &abbRhs );

/// \brief Checks whether the two boxes are not identical.
AE_FORCEINLINE AEBOOL32 operator!= ( const AEAlignedBoundingBox &abbLhs, const AEAlignedBoundingBox &abbRhs );

#include "AEAlignedBoundingBox.inl"

#endif // _AEALIGNEDBOUNDINGBOX_H_
